/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.query.expression.visitor;

import java.util.ArrayList;
import java.util.List;

import edu.ucsd.db.bassarisk.exception.VisitorException;
import edu.ucsd.db.bassarisk.query.ValidationTraversalLog;
import edu.ucsd.db.bassarisk.query.expression.Expressions.BooleanAnd;
import edu.ucsd.db.bassarisk.query.expression.Expressions.BooleanNot;
import edu.ucsd.db.bassarisk.query.expression.Expressions.Expression;
import edu.ucsd.db.bassarisk.query.expression.Expressions.NonterminalExpr;

/**
 * Certifies boolean safety by checking for unguarded NOT expressions.
 * 
 * To be guarded, each boolean NOT expression must must be the immediate
 * descendant of a boolean AND expression.
 * 
 * Furthermore, boolean NOT is never acceptable as a sub-expression to a
 * positional predicate. It is unsafe to make claims about the position of
 * tokens unmatched.
 */
public class BooleanSaftyValidationTraversal extends
    ExpressionTreeTransformation<ValidationTraversalLog> {
  /**
   * If parameter ex is a boolean not expression, then add an error to the
   * traversal arguments object.
   * 
   * @param ex
   * @param arg
   */
  private void assertNotBooleanNot(final Expression ex,
                                   final ValidationTraversalLog arg) {
    if (ex instanceof BooleanNot) {
      String msg =
          "Unsafe use of %s. All uses of %s must be guarded (a direct child) "
              + "of a boolean AND expression.";
      arg.addError(ex, String.format(msg, ex.friendlyName(), ex.friendlyName()));
    }
  }

  @Override
  public Expression visitBooleanAnd(final BooleanAnd expr,
                                    final ValidationTraversalLog arg)
      throws VisitorException {
    boolean containsPositiveSubExpression = false;
    List<Expression> oldSubExprs = expr.getSubExprs();
    List<Expression> newSubExprs = new ArrayList<Expression>();
    for (Expression oldSubExpr : oldSubExprs) {
      Expression newSubExpr = oldSubExpr.accept(this, arg);
      newSubExprs.add(newSubExpr);
      containsPositiveSubExpression |= !(newSubExpr instanceof BooleanNot);
    }
    if (!containsPositiveSubExpression) {
      String msg =
          "Unsafe use of boolean negation. Each use of %s must have at "
              + "least one positive (non-negative) sub expression.";
      arg.addError(expr, String.format(msg, expr.friendlyName()));
    }
    return expr.transform(newSubExprs);
  }

  @Override
  public Expression visitNonterminalExpr(final NonterminalExpr expr,
                                         final ValidationTraversalLog arg)
      throws VisitorException {
    List<Expression> oldSubExprs = expr.getSubExprs();
    List<Expression> newSubExprs = new ArrayList<Expression>();
    for (Expression oldExpr : oldSubExprs) {
      Expression newExpr = oldExpr.accept(this, arg);
      this.assertNotBooleanNot(newExpr, arg);
      newSubExprs.add(newExpr);
    }
    return expr.transform(newSubExprs);
  }
}
